package com.pinfly.common.http;

import java.net.Socket;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.cert.X509Certificate;

/**
 * Class that encapsulates information about a client connection for use by
 * application in HttpServer.
 */
public class ClientConnection
{
    private boolean m_isSSL = false;
    private X509Certificate[] m_certChain = null;
    private String m_ipAddress = null;

    /**
     * Constructor for non-secure connections.
     * 
     * @param s the socket connected to the client
     */
    public ClientConnection (Socket s)
    {
        setIPAddress (s);
    }

    /**
     * Construct ClientConnection with specified attributes
     * 
     * @param s The socket connected to the client
     * @param sslFlag <code>true</code> if client connection is over SSL
     * @param certChain Array of certificates that identifies the client. The
     *            first entry in the array is the client's certificate, the next
     *            is the certificate for the signer of the client certificate,
     *            and so on. The final cert in the array is a self-signed cert
     *            from a certificate authority.
     */
    public ClientConnection (Socket s, boolean sslFlag, X509Certificate[] certChain)
    {
        setIPAddress (s);
        m_isSSL = sslFlag;
        if (m_isSSL)
        {
            // Can't have certificates if not SSL
            m_certChain = certChain;
        }
    }

    /**
     * Set the client IP address.
     * 
     * @param s the socket connected to the client
     */
    private void setIPAddress (Socket s)
    {
        m_ipAddress = (s != null) ? s.getInetAddress ().getHostAddress () : "0.0.0.0";
    }

    /**
     * Get client IP address.
     * 
     * @return The IP address of the client formatted as as String: %d.%d.%d.%d
     */
    public String getIPAddress ()
    {
        return m_ipAddress;
    }

    /**
     * Is client connected using SSL?
     * 
     * @return <code>true</code> if connection is using SSL <code>false</code>
     *         otherwise
     */
    public boolean isSSL ()
    {
        return m_isSSL;
    }

    /**
     * Did client provide certificate?
     * 
     * @return <code>true</code> if the client provided a certificate chain as
     *         part of the SSL connection <code>false</code> otherwise
     */
    public boolean haveClientCert ()
    {
        return m_certChain != null;
    }

    /**
     * Get the client certificate chain. Returns the certificate chain used to
     * authenticate the client. The first item in the array is the client's
     * certficiate. Each succeeding item is the certificate of the entity that
     * issued the previous certificate. The final entry is the self-signed
     * certificate of a Certificate Authority.
     * 
     * @return the client certificate chain
     * @throws SSLPeerUnverifiedException if the client certificate chain is not
     *             available
     */
    public X509Certificate[] getClientCertChain () throws SSLPeerUnverifiedException
    {
        if (m_certChain == null)
        {
            throw new SSLPeerUnverifiedException ("No client certificate available");
        }
        return m_certChain;
    }

    /**
     * Validate the client certificate against a set of ids. Checks the list of
     * ids against the subject name field in the client certificate. If any of
     * the ids are present, returns <code>true</code>. If none of the ids are
     * present, or there is no client certificate, returns <code>false</code>.
     * <p>
     * The ids should be in the form of <code>name=value</code>, where
     * <code>name</code> is one of the parts of an X500 name (e.g. o, ou, l,
     * etc.). The value is the value of that part. For example
     * <code>ou=CCOW.ContextManager</code>.
     * 
     * @param ids The String array of ids to check for.
     * @return <code>true</code> if any of the ids are in the subject name field
     *         of the client certificate, <code>false</code> otherwise
     * @see #validateClient(String)
     */
    public boolean validateClient (String[] ids)
    {
        boolean retVal = false;
        if (ids != null && m_certChain != null)
        {
            try
            {
                SSLUtil.validateCert (m_certChain[0], ids);
                retVal = true;
            }
            catch (MSHttpInvalidCertificateException e)
            {
                // validation failed
            }
        }
        return retVal;
    }

    /**
     * Validate the client certificate against an id. This is a convenience
     * function equivalent to <code>validateClient (new String[] {id})</code>.
     * 
     * @param id The id to check for.
     * @return <code>true</code> if the id is in the subject name field of the
     *         client certificate, <code>false</code> otherwise
     * @see #validateClient(String[])
     */
    public boolean validateClient (String id)
    {
        return validateClient (new String[]
        { id });
    }

}
